module user_data


entity User {
	name		::	String(id, validate(findUser(name)==null, "Username is taken"))
	pass		::	Secret
	realname	::	String(validate(realname.length() != 0, "Empty real name"))
	bmentries	<>	List<BookmarkEntry>
	tgentries	<>	List<TagEntry>
	
	function bmentries_find(url: URL) : BookmarkEntry {
	  for(bmentry: BookmarkEntry in this.bmentries) {
	    if(bmentry.bm.url == url) {
	      return bmentry;
	    }
	  }
	  return null;
	}
	
	function bmentries_get(popsort: Bool) : List<BookmarkEntry> {
	  if(popsort) {
	    return [bmentry | bmentry: BookmarkEntry in this.bmentries order by bmentry.popularity desc];
	  }
	  return [bmentry | bmentry: BookmarkEntry in this.bmentries order by bmentry.date desc]; 
	}
	
	function bmentries_add(title: String, bm: Bookmark, tagnames: String) {
	  // XXX: this is where new BookmarkEntry entities get created
	  var bmentry := BookmarkEntry{title:=title, date:=now(), popularity:=0};
	  this.bmentries.add(bmentry);
	  bm.bmentries_add(bmentry);
	  this.settagnames(bmentry, tagnames);
	  bmentry.save();
	}

	function bmentries_remove(bmentry: BookmarkEntry) {
	  bmentry.bm.bmentries_remove(bmentry);
	  this.settagnames(bmentry, "");
	  this.bmentries.remove(bmentry);
	}

	function bmentries_edit(bmentry: BookmarkEntry, title: String, newtagnames: String) {
	  bmentry.title := title;
	  this.settagnames(bmentry, newtagnames);
	}
	
	function bmentries_setpopularity(bmentry: BookmarkEntry, val: Int) {
	  bmentry.popularity := val;
	}

	function settagnames(bmentry: BookmarkEntry, newtagnames: String) {
	  newtagnames := newtagnames.toUpperCase();
	  // resolve tag names from `newtagnames' to Tag entities in `newtagsset'
	  var newtagsset: Set<Tag>;
	  for(newtagname: String in newtagnames.split(" ") where (newtagname.length() > 1)) {
	    var newtag := findTag(newtagname);
	    if(newtag == null) {
	      // XXX: this is where new Tag entities get created
	      newtag := Tag{name:=newtagname};
	      newtag.save();
	    }
	    newtagsset.add(newtag);
	  }
	  // existing associations to tags which are not present in newtagsset, must go
	  for(tgentry: TagEntry in bmentry.tgentries) {
	    var found := false;
	    // is tgentry.tag a member of newtagsset?
	    for(newtag: Tag in newtagsset) {
	      if(tgentry.tag == newtag) {
	        found := true;
	      }
	    }
	    if(!found) {
	      // no: remove bmentry from tgentry
	      this.tgentries_bmentry_rem(tgentry, bmentry);
	    }
	  }
	  
	  // add tag associations we dont already have
	  for(tag: Tag in newtagsset) {
	    // does this bmentry belong to a tgentry with this tag?
	    var existtgentry : TagEntry := null;
	    for(tgentry: TagEntry in this.tgentries) {
	      if(tgentry.tag == tag) {
	        existtgentry := tgentry;
	      }
	    }
	    if(existtgentry == null) {
	      // XXX: this is where new TagEntry entities get created
	      this.tgentries_add(tag, bmentry);
	    } else {
	      if(!(bmentry in existtgentry.bmentries)) { 
	        this.tgentries_bmentry_add(existtgentry, bmentry);
	      }
	    }
	  }
	  
	}

	function gettagnames(bmentry: BookmarkEntry) : String {
	  var tagnames: String;
	  // this bmentry belongs to zero or more tagentries
	  for(tag: Tag in [tgentry.tag | tgentry: TagEntry in bmentry.tgentries]) {
	    tagnames := tagnames + tag.name + " ";
	  }
	  return tagnames;
	}
	
	function tgentries_bmentry_add(tgentry: TagEntry, bmentry: BookmarkEntry) {
	  tgentry.bmentries.add(bmentry);
	  tgentry.popularity := tgentry.bmentries.length;
	}

	function tgentries_bmentry_rem(tgentry: TagEntry, bmentry: BookmarkEntry) {
	  bmentry.tgentries.remove(tgentry);
	  tgentry.bmentries.remove(bmentry);
	  tgentry.popularity := tgentry.bmentries.length;
	  if(tgentry.popularity == 0) {
	    tgentry.tag.tgentries_remove(tgentry);
	    this.tgentries.remove(tgentry);
	  }
	}
	
	function tgentries_add(tag: Tag, bmentry: BookmarkEntry) {
	  var tgentry := TagEntry{};
	  tgentry.bmentries.add(bmentry);
	  tgentry.popularity := tgentry.bmentries.length;
	  
	  tag.tgentries_add(tgentry);
	  this.tgentries.add(tgentry);
	}

}

entity BookmarkEntry {
	title		::	String
	user		->	User		(inverse=User.bmentries)
	bm		->	Bookmark 	(inverse=Bookmark.bmentries)
	date		::	Date
	popularity	::	Int
	
	tgentries	->	List<TagEntry>	(inverse=TagEntry.bmentries)
		
	function matches(filt: String) : Bool {
		return (this.title.contains(filt) || this.bm.url.contains(filt));
	}

}


entity TagEntry {
	tag		->	Tag	(inverse=Tag.tgentries)
	user		->	User	(inverse=User.tgentries)
	bmentries	->	Set<BookmarkEntry>
	popularity	::	Int // equivalent to bmentries.length
}